/**
 * @author mrdoob / http://mrdoob.com/
 */

THREE.SpriteCanvasMaterial = function(parameters) {

    THREE.Material.call(this);

    this.type = 'SpriteCanvasMaterial';

    this.color = new THREE.Color(0xffffff);
    this.program = function() {};

    this.setValues(parameters);

};

THREE.SpriteCanvasMaterial.prototype = Object.create(THREE.Material.prototype);
THREE.SpriteCanvasMaterial.prototype.constructor = THREE.SpriteCanvasMaterial;
THREE.SpriteCanvasMaterial.prototype.isSpriteCanvasMaterial = true;

THREE.SpriteCanvasMaterial.prototype.clone = function() {

    var material = new THREE.SpriteCanvasMaterial();

    material.copy(this);
    material.color.copy(this.color);
    material.program = this.program;

    return material;

};

//

THREE.CanvasRenderer = function(parameters) {

    console.log('THREE.CanvasRenderer', THREE.REVISION);

    parameters = parameters || {};

    var _this = this,
        _renderData, _elements, _lights,
        _projector = new THREE.Projector(),

        _canvas = parameters.canvas !== undefined ?
        parameters.canvas :
        document.createElement('canvas'),

        _canvasWidth = _canvas.width,
        _canvasHeight = _canvas.height,
        _canvasWidthHalf = Math.floor(_canvasWidth / 2),
        _canvasHeightHalf = Math.floor(_canvasHeight / 2),

        _viewportX = 0,
        _viewportY = 0,
        _viewportWidth = _canvasWidth,
        _viewportHeight = _canvasHeight,

        _pixelRatio = 1,

        _context = _canvas.getContext('2d', {
            alpha: parameters.alpha === true
        }),

        _clearColor = new THREE.Color(0x000000),
        _clearAlpha = parameters.alpha === true ? 0 : 1,

        _contextGlobalAlpha = 1,
        _contextGlobalCompositeOperation = 0,
        _contextStrokeStyle = null,
        _contextFillStyle = null,
        _contextLineWidth = null,
        _contextLineCap = null,
        _contextLineJoin = null,
        _contextLineDash = [],

        _v1, _v2, _v3,

        _v1x, _v1y, _v2x, _v2y, _v3x, _v3y,

        _color = new THREE.Color(),

        _diffuseColor = new THREE.Color(),
        _emissiveColor = new THREE.Color(),

        _lightColor = new THREE.Color(),

        _patterns = {},

        _uvs,
        _uv1x, _uv1y, _uv2x, _uv2y, _uv3x, _uv3y,

        _clipBox = new THREE.Box2(),
        _clearBox = new THREE.Box2(),
        _elemBox = new THREE.Box2(),

        _ambientLight = new THREE.Color(),
        _directionalLights = new THREE.Color(),
        _pointLights = new THREE.Color(),

        _vector3 = new THREE.Vector3(), // Needed for PointLight
        _centroid = new THREE.Vector3(),
        _normal = new THREE.Vector3(),
        _normalViewMatrix = new THREE.Matrix3();

    /* TODO
    _canvas.mozImageSmoothingEnabled = false;
    _canvas.webkitImageSmoothingEnabled = false;
    _canvas.msImageSmoothingEnabled = false;
    _canvas.imageSmoothingEnabled = false;
    */

    // dash+gap fallbacks for Firefox and everything else

    if (_context.setLineDash === undefined) {

        _context.setLineDash = function() {};

    }

    this.domElement = _canvas;

    this.autoClear = true;
    this.sortObjects = true;
    this.sortElements = true;

    this.info = {

        render: {

            vertices: 0,
            faces: 0

        }

    };

    // API

    this.getContext = function() {

        return _context;

    };

    this.getContextAttributes = function() {

        return _context.getContextAttributes();

    };

    this.getPixelRatio = function() {

        return _pixelRatio;

    };

    this.setPixelRatio = function(value) {

        if (value !== undefined) _pixelRatio = value;

    };

    this.setSize = function(width, height, updateStyle) {

        _canvasWidth = width * _pixelRatio;
        _canvasHeight = height * _pixelRatio;

        _canvas.width = _canvasWidth;
        _canvas.height = _canvasHeight;

        _canvasWidthHalf = Math.floor(_canvasWidth / 2);
        _canvasHeightHalf = Math.floor(_canvasHeight / 2);

        if (updateStyle !== false) {

            _canvas.style.width = width + 'px';
            _canvas.style.height = height + 'px';

        }

        _clipBox.min.set(-_canvasWidthHalf, -_canvasHeightHalf);
        _clipBox.max.set(_canvasWidthHalf, _canvasHeightHalf);

        _clearBox.min.set(-_canvasWidthHalf, -_canvasHeightHalf);
        _clearBox.max.set(_canvasWidthHalf, _canvasHeightHalf);

        _contextGlobalAlpha = 1;
        _contextGlobalCompositeOperation = 0;
        _contextStrokeStyle = null;
        _contextFillStyle = null;
        _contextLineWidth = null;
        _contextLineCap = null;
        _contextLineJoin = null;

        this.setViewport(0, 0, width, height);

    };

    this.setViewport = function(x, y, width, height) {

        _viewportX = x * _pixelRatio;
        _viewportY = y * _pixelRatio;

        _viewportWidth = width * _pixelRatio;
        _viewportHeight = height * _pixelRatio;

    };

    this.setScissor = function() {};
    this.setScissorTest = function() {};

    this.setClearColor = function(color, alpha) {

        _clearColor.set(color);
        _clearAlpha = alpha !== undefined ? alpha : 1;

        _clearBox.min.set(-_canvasWidthHalf, -_canvasHeightHalf);
        _clearBox.max.set(_canvasWidthHalf, _canvasHeightHalf);

    };

    this.setClearColorHex = function(hex, alpha) {

        console.warn('THREE.CanvasRenderer: .setClearColorHex() is being removed. Use .setClearColor() instead.');
        this.setClearColor(hex, alpha);

    };

    this.getClearColor = function() {

        return _clearColor;

    };

    this.getClearAlpha = function() {

        return _clearAlpha;

    };

    this.getMaxAnisotropy = function() {

        return 0;

    };

    this.clear = function() {

        if (_clearBox.isEmpty() === false) {

            _clearBox.intersect(_clipBox);
            _clearBox.expandByScalar(2);

            _clearBox.min.x = _clearBox.min.x + _canvasWidthHalf;
            _clearBox.min.y = -_clearBox.min.y + _canvasHeightHalf; // higher y value !
            _clearBox.max.x = _clearBox.max.x + _canvasWidthHalf;
            _clearBox.max.y = -_clearBox.max.y + _canvasHeightHalf; // lower y value !

            if (_clearAlpha < 1) {

                _context.clearRect(
                    _clearBox.min.x | 0,
                    _clearBox.max.y | 0,
                    (_clearBox.max.x - _clearBox.min.x) | 0,
                    (_clearBox.min.y - _clearBox.max.y) | 0
                );

            }

            if (_clearAlpha > 0) {

                setOpacity(1);
                setBlending(THREE.NormalBlending);

                setFillStyle('rgba(' + Math.floor(_clearColor.r * 255) + ',' + Math.floor(_clearColor.g * 255) + ',' + Math.floor(_clearColor.b * 255) + ',' + _clearAlpha + ')');

                _context.fillRect(
                    _clearBox.min.x | 0,
                    _clearBox.max.y | 0,
                    (_clearBox.max.x - _clearBox.min.x) | 0,
                    (_clearBox.min.y - _clearBox.max.y) | 0
                );

            }

            _clearBox.makeEmpty();

        }

    };

    // compatibility

    this.clearColor = function() {};
    this.clearDepth = function() {};
    this.clearStencil = function() {};

    this.render = function(scene, camera) {

        if (camera.isCamera === undefined) {

            console.error('THREE.CanvasRenderer.render: camera is not an instance of THREE.Camera.');
            return;

        }

        var background = scene.background;

        if (background && background.isColor) {

            setOpacity(1);
            setBlending(THREE.NormalBlending);

            setFillStyle(background.getStyle());
            _context.fillRect(0, 0, _canvasWidth, _canvasHeight);

        } else if (this.autoClear === true) {

            this.clear();

        }

        _this.info.render.vertices = 0;
        _this.info.render.faces = 0;

        _context.setTransform(_viewportWidth / _canvasWidth, 0, 0, -_viewportHeight / _canvasHeight, _viewportX, _canvasHeight - _viewportY);
        _context.translate(_canvasWidthHalf, _canvasHeightHalf);

        _renderData = _projector.projectScene(scene, camera, this.sortObjects, this.sortElements);
        _elements = _renderData.elements;
        _lights = _renderData.lights;

        _normalViewMatrix.getNormalMatrix(camera.matrixWorldInverse);

        /* DEBUG
        setFillStyle( 'rgba( 0, 255, 255, 0.5 )' );
        _context.fillRect( _clipBox.min.x, _clipBox.min.y, _clipBox.max.x - _clipBox.min.x, _clipBox.max.y - _clipBox.min.y );
        */

        calculateLights();

        for (var e = 0, el = _elements.length; e < el; e++) {

            var element = _elements[e];

            var material = element.material;

            if (material === undefined || material.opacity === 0) continue;

            _elemBox.makeEmpty();

            if (element instanceof THREE.RenderableSprite) {

                _v1 = element;
                _v1.x *= _canvasWidthHalf;
                _v1.y *= _canvasHeightHalf;

                renderSprite(_v1, element, material);

            } else if (element instanceof THREE.RenderableLine) {

                _v1 = element.v1;
                _v2 = element.v2;

                _v1.positionScreen.x *= _canvasWidthHalf;
                _v1.positionScreen.y *= _canvasHeightHalf;
                _v2.positionScreen.x *= _canvasWidthHalf;
                _v2.positionScreen.y *= _canvasHeightHalf;

                _elemBox.setFromPoints([
                    _v1.positionScreen,
                    _v2.positionScreen
                ]);

                if (_clipBox.intersectsBox(_elemBox) === true) {

                    renderLine(_v1, _v2, element, material);

                }

            } else if (element instanceof THREE.RenderableFace) {

                _v1 = element.v1;
                _v2 = element.v2;
                _v3 = element.v3;

                if (_v1.positionScreen.z < -1 || _v1.positionScreen.z > 1) continue;
                if (_v2.positionScreen.z < -1 || _v2.positionScreen.z > 1) continue;
                if (_v3.positionScreen.z < -1 || _v3.positionScreen.z > 1) continue;

                _v1.positionScreen.x *= _canvasWidthHalf;
                _v1.positionScreen.y *= _canvasHeightHalf;
                _v2.positionScreen.x *= _canvasWidthHalf;
                _v2.positionScreen.y *= _canvasHeightHalf;
                _v3.positionScreen.x *= _canvasWidthHalf;
                _v3.positionScreen.y *= _canvasHeightHalf;

                if (material.overdraw > 0) {

                    expand(_v1.positionScreen, _v2.positionScreen, material.overdraw);
                    expand(_v2.positionScreen, _v3.positionScreen, material.overdraw);
                    expand(_v3.positionScreen, _v1.positionScreen, material.overdraw);

                }

                _elemBox.setFromPoints([
                    _v1.positionScreen,
                    _v2.positionScreen,
                    _v3.positionScreen
                ]);

                if (_clipBox.intersectsBox(_elemBox) === true) {

                    renderFace3(_v1, _v2, _v3, 0, 1, 2, element, material);

                }

            }

            /* DEBUG
            setLineWidth( 1 );
            setStrokeStyle( 'rgba( 0, 255, 0, 0.5 )' );
            _context.strokeRect( _elemBox.min.x, _elemBox.min.y, _elemBox.max.x - _elemBox.min.x, _elemBox.max.y - _elemBox.min.y );
            */

            _clearBox.union(_elemBox);

        }

        /* DEBUG
        setLineWidth( 1 );
        setStrokeStyle( 'rgba( 255, 0, 0, 0.5 )' );
        _context.strokeRect( _clearBox.min.x, _clearBox.min.y, _clearBox.max.x - _clearBox.min.x, _clearBox.max.y - _clearBox.min.y );
        */

        _context.setTransform(1, 0, 0, 1, 0, 0);

    };

    //

    function calculateLights() {

        _ambientLight.setRGB(0, 0, 0);
        _directionalLights.setRGB(0, 0, 0);
        _pointLights.setRGB(0, 0, 0);

        for (var l = 0, ll = _lights.length; l < ll; l++) {

            var light = _lights[l];
            var lightColor = light.color;

            if (light.isAmbientLight) {

                _ambientLight.add(lightColor);

            } else if (light.isDirectionalLight) {

                // for sprites

                _directionalLights.add(lightColor);

            } else if (light.isPointLight) {

                // for sprites

                _pointLights.add(lightColor);

            }

        }

    }

    function calculateLight(position, normal, color) {

        for (var l = 0, ll = _lights.length; l < ll; l++) {

            var light = _lights[l];

            _lightColor.copy(light.color);

            if (light.isDirectionalLight) {

                var lightPosition = _vector3.setFromMatrixPosition(light.matrixWorld).normalize();

                var amount = normal.dot(lightPosition);

                if (amount <= 0) continue;

                amount *= light.intensity;

                color.add(_lightColor.multiplyScalar(amount));

            } else if (light.isPointLight) {

                var lightPosition = _vector3.setFromMatrixPosition(light.matrixWorld);

                var amount = normal.dot(_vector3.subVectors(lightPosition, position).normalize());

                if (amount <= 0) continue;

                amount *= light.distance == 0 ? 1 : 1 - Math.min(position.distanceTo(lightPosition) / light.distance, 1);

                if (amount == 0) continue;

                amount *= light.intensity;

                color.add(_lightColor.multiplyScalar(amount));

            }

        }

    }

    function renderSprite(v1, element, material) {

        setOpacity(material.opacity);
        setBlending(material.blending);

        var scaleX = element.scale.x * _canvasWidthHalf;
        var scaleY = element.scale.y * _canvasHeightHalf;

        var dist = Math.sqrt(scaleX * scaleX + scaleY * scaleY); // allow for rotated sprite
        _elemBox.min.set(v1.x - dist, v1.y - dist);
        _elemBox.max.set(v1.x + dist, v1.y + dist);

        if (material.isSpriteMaterial) {

            var texture = material.map;

            if (texture !== null) {

                var pattern = _patterns[texture.id];

                if (pattern === undefined || pattern.version !== texture.version) {

                    pattern = textureToPattern(texture);
                    _patterns[texture.id] = pattern;

                }

                if (pattern.canvas !== undefined) {

                    setFillStyle(pattern.canvas);

                    var bitmap = texture.image;

                    var ox = bitmap.width * texture.offset.x;
                    var oy = bitmap.height * texture.offset.y;

                    var sx = bitmap.width * texture.repeat.x;
                    var sy = bitmap.height * texture.repeat.y;

                    var cx = scaleX / sx;
                    var cy = scaleY / sy;

                    _context.save();
                    _context.translate(v1.x, v1.y);
                    if (material.rotation !== 0) _context.rotate(material.rotation);
                    _context.translate(-scaleX / 2, -scaleY / 2);
                    _context.scale(cx, cy);
                    _context.translate(-ox, -oy);
                    _context.fillRect(ox, oy, sx, sy);
                    _context.restore();

                }

            } else {

                // no texture

                setFillStyle(material.color.getStyle());

                _context.save();
                _context.translate(v1.x, v1.y);
                if (material.rotation !== 0) _context.rotate(material.rotation);
                _context.scale(scaleX, -scaleY);
                _context.fillRect(-0.5, -0.5, 1, 1);
                _context.restore();

            }

        } else if (material.isSpriteCanvasMaterial) {

            setStrokeStyle(material.color.getStyle());
            setFillStyle(material.color.getStyle());

            _context.save();
            _context.translate(v1.x, v1.y);
            if (material.rotation !== 0) _context.rotate(material.rotation);
            _context.scale(scaleX, scaleY);

            material.program(_context);

            _context.restore();

        } else if (material.isPointsMaterial) {

            setFillStyle(material.color.getStyle());

            _context.save();
            _context.translate(v1.x, v1.y);
            if (material.rotation !== 0) _context.rotate(material.rotation);
            _context.scale(scaleX * material.size, -scaleY * material.size);
            _context.fillRect(-0.5, -0.5, 1, 1);
            _context.restore();

        }

        /* DEBUG
        setStrokeStyle( 'rgb(255,255,0)' );
        _context.beginPath();
        _context.moveTo( v1.x - 10, v1.y );
        _context.lineTo( v1.x + 10, v1.y );
        _context.moveTo( v1.x, v1.y - 10 );
        _context.lineTo( v1.x, v1.y + 10 );
        _context.stroke();
        */

    }

    function renderLine(v1, v2, element, material) {

        setOpacity(material.opacity);
        setBlending(material.blending);

        _context.beginPath();
        _context.moveTo(v1.positionScreen.x, v1.positionScreen.y);
        _context.lineTo(v2.positionScreen.x, v2.positionScreen.y);

        if (material.isLineBasicMaterial) {

            setLineWidth(material.linewidth);
            setLineCap(material.linecap);
            setLineJoin(material.linejoin);

            if (material.vertexColors !== THREE.VertexColors) {

                setStrokeStyle(material.color.getStyle());

            } else {

                var colorStyle1 = element.vertexColors[0].getStyle();
                var colorStyle2 = element.vertexColors[1].getStyle();

                if (colorStyle1 === colorStyle2) {

                    setStrokeStyle(colorStyle1);

                } else {

                    try {

                        var grad = _context.createLinearGradient(
                            v1.positionScreen.x,
                            v1.positionScreen.y,
                            v2.positionScreen.x,
                            v2.positionScreen.y
                        );
                        grad.addColorStop(0, colorStyle1);
                        grad.addColorStop(1, colorStyle2);

                    } catch (exception) {

                        grad = colorStyle1;

                    }

                    setStrokeStyle(grad);

                }

            }

            if (material.isLineDashedMaterial) {

                setLineDash([material.dashSize, material.gapSize]);

            }

            _context.stroke();
            _elemBox.expandByScalar(material.linewidth * 2);

            if (material.isLineDashedMaterial) {

                setLineDash([]);

            }

        }

    }

    function renderFace3(v1, v2, v3, uv1, uv2, uv3, element, material) {

        _this.info.render.vertices += 3;
        _this.info.render.faces++;

        setOpacity(material.opacity);
        setBlending(material.blending);

        _v1x = v1.positionScreen.x;
        _v1y = v1.positionScreen.y;
        _v2x = v2.positionScreen.x;
        _v2y = v2.positionScreen.y;
        _v3x = v3.positionScreen.x;
        _v3y = v3.positionScreen.y;

        drawTriangle(_v1x, _v1y, _v2x, _v2y, _v3x, _v3y);

        if ((material.isMeshLambertMaterial || material.isMeshPhongMaterial || material.isMeshStandardMaterial) && material.map === null) {

            _diffuseColor.copy(material.color);
            _emissiveColor.copy(material.emissive);

            if (material.vertexColors === THREE.FaceColors) {

                _diffuseColor.multiply(element.color);

            }

            _color.copy(_ambientLight);

            _centroid.copy(v1.positionWorld).add(v2.positionWorld).add(v3.positionWorld).divideScalar(3);

            calculateLight(_centroid, element.normalModel, _color);

            _color.multiply(_diffuseColor).add(_emissiveColor);

            material.wireframe === true ?
                strokePath(_color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin) :
                fillPath(_color);

        } else if (material.isMeshBasicMaterial || material.isMeshLambertMaterial || material.isMeshPhongMaterial || material.isMeshStandardMaterial) {

            if (material.map !== null) {

                var mapping = material.map.mapping;

                if (mapping === THREE.UVMapping) {

                    _uvs = element.uvs;
                    patternPath(_v1x, _v1y, _v2x, _v2y, _v3x, _v3y, _uvs[uv1].x, _uvs[uv1].y, _uvs[uv2].x, _uvs[uv2].y, _uvs[uv3].x, _uvs[uv3].y, material.map);

                }

            } else if (material.envMap !== null) {

                if (material.envMap.mapping === THREE.SphericalReflectionMapping) {

                    _normal.copy(element.vertexNormalsModel[uv1]).applyMatrix3(_normalViewMatrix);
                    _uv1x = 0.5 * _normal.x + 0.5;
                    _uv1y = 0.5 * _normal.y + 0.5;

                    _normal.copy(element.vertexNormalsModel[uv2]).applyMatrix3(_normalViewMatrix);
                    _uv2x = 0.5 * _normal.x + 0.5;
                    _uv2y = 0.5 * _normal.y + 0.5;

                    _normal.copy(element.vertexNormalsModel[uv3]).applyMatrix3(_normalViewMatrix);
                    _uv3x = 0.5 * _normal.x + 0.5;
                    _uv3y = 0.5 * _normal.y + 0.5;

                    patternPath(_v1x, _v1y, _v2x, _v2y, _v3x, _v3y, _uv1x, _uv1y, _uv2x, _uv2y, _uv3x, _uv3y, material.envMap);

                }

            } else {

                _color.copy(material.color);

                if (material.vertexColors === THREE.FaceColors) {

                    _color.multiply(element.color);

                }

                material.wireframe === true ?
                    strokePath(_color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin) :
                    fillPath(_color);

            }

        } else if (material.isMeshNormalMaterial) {

            _normal.copy(element.normalModel).applyMatrix3(_normalViewMatrix);

            _color.setRGB(_normal.x, _normal.y, _normal.z).multiplyScalar(0.5).addScalar(0.5);

            material.wireframe === true ?
                strokePath(_color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin) :
                fillPath(_color);

        } else {

            _color.setRGB(1, 1, 1);

            material.wireframe === true ?
                strokePath(_color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin) :
                fillPath(_color);

        }

    }

    //

    function drawTriangle(x0, y0, x1, y1, x2, y2) {

        _context.beginPath();
        _context.moveTo(x0, y0);
        _context.lineTo(x1, y1);
        _context.lineTo(x2, y2);
        _context.closePath();

    }

    function strokePath(color, linewidth, linecap, linejoin) {

        setLineWidth(linewidth);
        setLineCap(linecap);
        setLineJoin(linejoin);
        setStrokeStyle(color.getStyle());

        _context.stroke();

        _elemBox.expandByScalar(linewidth * 2);

    }

    function fillPath(color) {

        setFillStyle(color.getStyle());
        _context.fill();

    }

    function textureToPattern(texture) {

        if (texture.version === 0 ||
            texture instanceof THREE.CompressedTexture ||
            texture instanceof THREE.DataTexture) {

            return {
                canvas: undefined,
                version: texture.version
            };

        }

        var image = texture.image;

        if (image.complete === false) {

            return {
                canvas: undefined,
                version: 0
            };

        }

        var repeatX = texture.wrapS === THREE.RepeatWrapping || texture.wrapS === THREE.MirroredRepeatWrapping;
        var repeatY = texture.wrapT === THREE.RepeatWrapping || texture.wrapT === THREE.MirroredRepeatWrapping;

        var mirrorX = texture.wrapS === THREE.MirroredRepeatWrapping;
        var mirrorY = texture.wrapT === THREE.MirroredRepeatWrapping;

        //

        var canvas = document.createElement('canvas');
        canvas.width = image.width * (mirrorX ? 2 : 1);
        canvas.height = image.height * (mirrorY ? 2 : 1);

        var context = canvas.getContext('2d');
        context.setTransform(1, 0, 0, -1, 0, image.height);
        context.drawImage(image, 0, 0);

        if (mirrorX === true) {

            context.setTransform(-1, 0, 0, -1, image.width, image.height);
            context.drawImage(image, -image.width, 0);

        }

        if (mirrorY === true) {

            context.setTransform(1, 0, 0, 1, 0, 0);
            context.drawImage(image, 0, image.height);

        }

        if (mirrorX === true && mirrorY === true) {

            context.setTransform(-1, 0, 0, 1, image.width, 0);
            context.drawImage(image, -image.width, image.height);

        }

        var repeat = 'no-repeat';

        if (repeatX === true && repeatY === true) {

            repeat = 'repeat';

        } else if (repeatX === true) {

            repeat = 'repeat-x';

        } else if (repeatY === true) {

            repeat = 'repeat-y';

        }

        var pattern = _context.createPattern(canvas, repeat);

        if (texture.onUpdate) texture.onUpdate(texture);

        return {
            canvas: pattern,
            version: texture.version
        };

    }

    function patternPath(x0, y0, x1, y1, x2, y2, u0, v0, u1, v1, u2, v2, texture) {

        var pattern = _patterns[texture.id];

        if (pattern === undefined || pattern.version !== texture.version) {

            pattern = textureToPattern(texture);
            _patterns[texture.id] = pattern;

        }

        if (pattern.canvas !== undefined) {

            setFillStyle(pattern.canvas);

        } else {

            setFillStyle('rgba( 0, 0, 0, 1)');
            _context.fill();
            return;

        }

        // http://extremelysatisfactorytotalitarianism.com/blog/?p=2120

        var a, b, c, d, e, f, det, idet,
            offsetX = texture.offset.x / texture.repeat.x,
            offsetY = texture.offset.y / texture.repeat.y,
            width = texture.image.width * texture.repeat.x,
            height = texture.image.height * texture.repeat.y;

        u0 = (u0 + offsetX) * width;
        v0 = (v0 + offsetY) * height;

        u1 = (u1 + offsetX) * width;
        v1 = (v1 + offsetY) * height;

        u2 = (u2 + offsetX) * width;
        v2 = (v2 + offsetY) * height;

        x1 -= x0;
        y1 -= y0;
        x2 -= x0;
        y2 -= y0;

        u1 -= u0;
        v1 -= v0;
        u2 -= u0;
        v2 -= v0;

        det = u1 * v2 - u2 * v1;

        if (det === 0) return;

        idet = 1 / det;

        a = (v2 * x1 - v1 * x2) * idet;
        b = (v2 * y1 - v1 * y2) * idet;
        c = (u1 * x2 - u2 * x1) * idet;
        d = (u1 * y2 - u2 * y1) * idet;

        e = x0 - a * u0 - c * v0;
        f = y0 - b * u0 - d * v0;

        _context.save();
        _context.transform(a, b, c, d, e, f);
        _context.fill();
        _context.restore();

    }

    /*
    function clipImage( x0, y0, x1, y1, x2, y2, u0, v0, u1, v1, u2, v2, image ) {
        // http://extremelysatisfactorytotalitarianism.com/blog/?p=2120
        var a, b, c, d, e, f, det, idet,
        width = image.width - 1,
        height = image.height - 1;
        u0 *= width; v0 *= height;
        u1 *= width; v1 *= height;
        u2 *= width; v2 *= height;
        x1 -= x0; y1 -= y0;
        x2 -= x0; y2 -= y0;
        u1 -= u0; v1 -= v0;
        u2 -= u0; v2 -= v0;
        det = u1 * v2 - u2 * v1;
        idet = 1 / det;
        a = ( v2 * x1 - v1 * x2 ) * idet;
        b = ( v2 * y1 - v1 * y2 ) * idet;
        c = ( u1 * x2 - u2 * x1 ) * idet;
        d = ( u1 * y2 - u2 * y1 ) * idet;
        e = x0 - a * u0 - c * v0;
        f = y0 - b * u0 - d * v0;
        _context.save();
        _context.transform( a, b, c, d, e, f );
        _context.clip();
        _context.drawImage( image, 0, 0 );
        _context.restore();
    }
    */

    // Hide anti-alias gaps

    function expand(v1, v2, pixels) {

        var x = v2.x - v1.x,
            y = v2.y - v1.y,
            det = x * x + y * y,
            idet;

        if (det === 0) return;

        idet = pixels / Math.sqrt(det);

        x *= idet;
        y *= idet;

        v2.x += x;
        v2.y += y;
        v1.x -= x;
        v1.y -= y;

    }

    // Context cached methods.

    function setOpacity(value) {

        if (_contextGlobalAlpha !== value) {

            _context.globalAlpha = value;
            _contextGlobalAlpha = value;

        }

    }

    function setBlending(value) {

        if (_contextGlobalCompositeOperation !== value) {

            if (value === THREE.NormalBlending) {

                _context.globalCompositeOperation = 'source-over';

            } else if (value === THREE.AdditiveBlending) {

                _context.globalCompositeOperation = 'lighter';

            } else if (value === THREE.SubtractiveBlending) {

                _context.globalCompositeOperation = 'darker';

            } else if (value === THREE.MultiplyBlending) {

                _context.globalCompositeOperation = 'multiply';

            }

            _contextGlobalCompositeOperation = value;

        }

    }

    function setLineWidth(value) {

        if (_contextLineWidth !== value) {

            _context.lineWidth = value;
            _contextLineWidth = value;

        }

    }

    function setLineCap(value) {

        // "butt", "round", "square"

        if (_contextLineCap !== value) {

            _context.lineCap = value;
            _contextLineCap = value;

        }

    }

    function setLineJoin(value) {

        // "round", "bevel", "miter"

        if (_contextLineJoin !== value) {

            _context.lineJoin = value;
            _contextLineJoin = value;

        }

    }

    function setStrokeStyle(value) {

        if (_contextStrokeStyle !== value) {

            _context.strokeStyle = value;
            _contextStrokeStyle = value;

        }

    }

    function setFillStyle(value) {

        if (_contextFillStyle !== value) {

            _context.fillStyle = value;
            _contextFillStyle = value;

        }

    }

    function setLineDash(value) {

        if (_contextLineDash.length !== value.length) {

            _context.setLineDash(value);
            _contextLineDash = value;

        }

    }

};
